﻿using System;
using System.Globalization;
using System.Reflection;
using System.Reflection.Emit;

using FireBus.Contracts;
using System.Collections.Generic;


namespace FireBus.Isolation
{
	public class ExtensionAdapterBuilder
	{
		private const String InstanceFieldName = "_Instance";

		private const String NotifyEventName = "Notify";

		private const String SendEventName = "Send";

		private const String NotifyEventHandlerNameTemplate = "On{0}{1}";

		private const String SendEventHandlerNameTemplate = "On{0}{1}";

		private readonly Type _ExtensionType;

		private Type _AdapterType;

		private readonly Object _Extension;

		private readonly ExtensionAdapter _Adapter;

		private TypeBuilder _AdapterTypeBuilder;

		private FieldBuilder _InstanceField;

		private readonly static Type _EventListenerInterfaceType = typeof(IEventListener<>);

		private readonly static Type _RequestListenerInterfaceType = typeof(IRequestListener<,>);

		private readonly static Type _EventNotifierInterfaceType = typeof(IEventNotifier<>);

		private readonly static Type _RequestSenderInterfaceType = typeof(IRequestSender<,>);

		private static readonly List<Type> _ImplementedInterfaces =
			new List<Type>(
				new[]
					{
						_EventListenerInterfaceType,
						_RequestListenerInterfaceType,
						_EventNotifierInterfaceType,
						_RequestSenderInterfaceType
					});


		public ExtensionAdapter Adapter
		{
			get { return _Adapter; }
		}

		public Type ExtensionType
		{
			get { return _ExtensionType; }
		}


		public ExtensionAdapterBuilder(ExtensionName extensionName, TypeName name)
		{
			_ExtensionType = Type.GetType(name, true);
			_Extension = Activator.CreateInstance(_ExtensionType);

			CreateType();
			var constructor =
				_AdapterType.GetConstructor(
					new[]
						{
							_ExtensionType
						});
			_Adapter = (ExtensionAdapter)constructor.Invoke(
				new []
				{
					_Extension
				});
			_Adapter.Metadata = ExtensionMetadata.FromType(
				extensionName, _ExtensionType);
		}

		private void CreateType()
		{
			CreateTypeBuilder();
			CreateFields();
			ImplementExtensionInterfaces();
			CreateConstructor();
			
			_AdapterType = _AdapterTypeBuilder.CreateType();
		}

		private void CreateTypeBuilder()
		{
			var assemblyName = String.Format(
				CultureInfo.InvariantCulture, "{0}_Adapter", _ExtensionType.FullName);
			var assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(
				new AssemblyName(assemblyName),
				AssemblyBuilderAccess.RunAndSave);
			var moduleBuilder = assemblyBuilder.DefineDynamicModule(
				assemblyName,
				String.Format(
					CultureInfo.InvariantCulture, "{0}.dll", assemblyName),
				true);
			var typeBuilder = moduleBuilder.DefineType(
				_ExtensionType.Name + "_Adapter", TypeAttributes.Public);

			typeBuilder.SetParent(typeof(ExtensionAdapter));

			_AdapterTypeBuilder = typeBuilder;
		}

		private void CreateFields()
		{
			_InstanceField =
				_AdapterTypeBuilder.DefineField(
					InstanceFieldName,
					_ExtensionType,
					FieldAttributes.Private);
		}

		private void ImplementExtensionInterfaces()
		{
			foreach(var interfaceType in _ExtensionType.GetInterfaces())
			{
				if (!interfaceType.IsGenericType)
				{
					continue;
				}
				var genericInterfaceDefinition = interfaceType.GetGenericTypeDefinition();
				if (!_ImplementedInterfaces.Contains(genericInterfaceDefinition))
				{
					continue;
				}
				ImplementInterface(interfaceType);
			}
		}

		private void ImplementInterface(Type interfaceType)
		{
			_AdapterTypeBuilder.AddInterfaceImplementation(interfaceType);
			
			var methods = interfaceType.GetMethods();
			foreach (var method in methods)
			{
				ImplementInterfaceMethodExplicitly(method);
			}
		}

		private void ImplementInterfaceMethodExplicitly(
			MethodInfo method)
		{
			var interfaceType = method.DeclaringType;
			var parameters = method.GetParameters();
			var parameterTypes = new Type[parameters.Length];
			for (var i = 0; i < parameters.Length; i++)
			{
				parameterTypes[i] = parameters[i].ParameterType;
			}
			var methodName =
				String.Format(
					CultureInfo.InvariantCulture,
					"{0}.{1}",
					interfaceType.Name,
					method.Name);
			var methodImplementation = _AdapterTypeBuilder.DefineMethod(
				methodName,
				MethodAttributes.Private | MethodAttributes.HideBySig |
				MethodAttributes.NewSlot | MethodAttributes.Virtual |
				MethodAttributes.Final,
				method.ReturnType,
				parameterTypes);
			ILGenerator il = methodImplementation.GetILGenerator();
			il.Emit(OpCodes.Ldarg_0);
			il.Emit(OpCodes.Ldfld, _InstanceField);
			il.Emit(OpCodes.Castclass, interfaceType);
			for (var i = 0; i < parameters.Length; i++)
			{
				il.Emit(OpCodes.Ldarg, i + 1);
			}
			il.Emit(OpCodes.Call, method);
			
			il.Emit(OpCodes.Ret);

			_AdapterTypeBuilder.DefineMethodOverride(
				methodImplementation, method);
		}

		private void CreateConstructor()
		{
			var constructorBuilder =
				_AdapterTypeBuilder.DefineConstructor(
					MethodAttributes.Public,
					CallingConventions.Standard,
					new[]
						{
							_ExtensionType
						});
			var il = constructorBuilder.GetILGenerator();

			SetFields(il);
			AddEventHandlers(il);
			il.Emit(OpCodes.Ret);
		}

		private void SetFields(ILGenerator il)
		{
			il.Emit(OpCodes.Ldarg_0);
			il.Emit(OpCodes.Ldarg_1);
			il.Emit(OpCodes.Stfld, _InstanceField);
		}

		private void AddEventHandlers(ILGenerator il)
		{
			foreach (var interfaceType in _ExtensionType.GetInterfaces())
			{
				if (!interfaceType.IsGenericType)
				{
					continue;
				}
				var genericInterfaceDefinition = interfaceType.GetGenericTypeDefinition();
				var isEventNotifier = _EventNotifierInterfaceType == genericInterfaceDefinition;
				if (isEventNotifier)
				{
					AddNotifyEventHandler(interfaceType, il);
				}
				var isRequestSender = _RequestSenderInterfaceType == genericInterfaceDefinition;
				if (isRequestSender)
				{
					AddSendEventHandler(interfaceType, il);
				}
			}
		}

		private void AddNotifyEventHandler(Type interfaceType, ILGenerator il)
		{
			var eventInfo =
				interfaceType.GetEvent(NotifyEventName);
			if (eventInfo == null)
			{
				return;
			}
			var addMethod = eventInfo.GetAddMethod();
			var eventHandler = ImplementNotifyEventHandler(eventInfo);
			var eventType = interfaceType.GetGenericArguments()[0];
			var delegateType = typeof (NotifyEventHandler<>).MakeGenericType(eventType);
			var delegateConstructor = delegateType.GetConstructor(
				new[]
					{
						typeof(Object),
						typeof(IntPtr)
					});
			il.Emit(OpCodes.Ldarg_0);
			il.Emit(OpCodes.Ldfld, _InstanceField);
			il.Emit(OpCodes.Ldarg_0);
			il.Emit(OpCodes.Ldftn, eventHandler);
			il.Emit(OpCodes.Newobj, delegateConstructor);
			il.Emit(OpCodes.Callvirt, addMethod);
		}

		private MethodBuilder ImplementNotifyEventHandler(EventInfo eventInfo)
		{
			var interfaceType = eventInfo.DeclaringType;
			var methodName =
				String.Format(
					CultureInfo.InvariantCulture,
					NotifyEventHandlerNameTemplate,
					interfaceType.Name,
					NotifyEventName);
			var eventType = interfaceType.GetGenericArguments()[0];
			var methodImplementation = _AdapterTypeBuilder.DefineMethod(
				methodName,
				MethodAttributes.Private | MethodAttributes.HideBySig |
				MethodAttributes.NewSlot | MethodAttributes.Virtual |
				MethodAttributes.Final,
				null,
				new[]
					{
						typeof (Object),
						typeof(NotifyEventArgs<>).MakeGenericType(eventType)
					});
			ILGenerator il = methodImplementation.GetILGenerator();
			var notifyMethod = _AdapterTypeBuilder.BaseType.GetMethod(
				"NotifyAll", BindingFlags.NonPublic | BindingFlags.Static).MakeGenericMethod(eventType);
			il.Emit(OpCodes.Ldarg_2);
			il.Emit(OpCodes.Call, notifyMethod);
			il.Emit(OpCodes.Ret);

			return methodImplementation;
		}

		private void AddSendEventHandler(Type interfaceType, ILGenerator il)
		{
			var eventInfo =
				interfaceType.GetEvent(SendEventName);
			if (eventInfo == null)
			{
				return;
			}
			var addMethod = eventInfo.GetAddMethod();
			var eventHandler = ImplementSendEventHandler(eventInfo);
			var genericArguments = interfaceType.GetGenericArguments();
			var requestType = genericArguments[0];
			var responseType = genericArguments[1];
			var delegateType = typeof(SendEventHandler<,>).MakeGenericType(requestType, responseType);
			var delegateConstructor = delegateType.GetConstructor(
				new[]
					{
						typeof(Object),
						typeof(IntPtr)
					});
			il.Emit(OpCodes.Ldarg_0);
			il.Emit(OpCodes.Ldfld, _InstanceField);
			il.Emit(OpCodes.Ldarg_0);
			il.Emit(OpCodes.Ldftn, eventHandler);
			il.Emit(OpCodes.Newobj, delegateConstructor);
			il.Emit(OpCodes.Callvirt, addMethod);
		}

		private MethodBuilder ImplementSendEventHandler(EventInfo eventInfo)
		{
			var interfaceType = eventInfo.DeclaringType;
			var methodName =
				String.Format(
					CultureInfo.InvariantCulture,
					SendEventHandlerNameTemplate,
					interfaceType.Name,
					SendEventName);
			var genericArguments = interfaceType.GetGenericArguments();
			var requestType = genericArguments[0];
			var responseType = genericArguments[1];
			var methodImplementation = _AdapterTypeBuilder.DefineMethod(
				methodName,
				MethodAttributes.Private | MethodAttributes.HideBySig |
				MethodAttributes.NewSlot | MethodAttributes.Virtual |
				MethodAttributes.Final,
				null,
				new[]
					{
						typeof (Object),
						typeof(SendEventArgs<,>).MakeGenericType(requestType, responseType)
					});
			ILGenerator il = methodImplementation.GetILGenerator();
			var notifyMethod = _AdapterTypeBuilder.BaseType.GetMethod(
				"Send", BindingFlags.NonPublic | BindingFlags.Static).MakeGenericMethod(requestType, responseType);
			il.Emit(OpCodes.Ldarg_2);
			il.Emit(OpCodes.Call, notifyMethod);
			il.Emit(OpCodes.Ret);

			return methodImplementation;
		}
	}
}
